# -*- coding: utf-8 -*-
# @Time    : 2024/7/11 16:22
# @Author  : yujiahao
# @File    : 15_fastapi_response_model.py
# @description:响应模型


from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class Item(BaseModel):
    name: str
    # 响应模型中具有默认值
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []


'''
你可以在任意的路径操作中使用 response_model 参数来声明用于响应的模型：

@app.get()
@app.post()
@app.put()
@app.delete()

注意，response_model是「装饰器」方法（get，post 等）的一个参数。不像之前的所有参数和请求体，它不属于路径操作函数。

它接收的类型与你将为 Pydantic 模型属性所声明的类型相同，因此它可以是一个 Pydantic 模型，但也可以是一个由 Pydantic 模型组成的 list，
例如 List[Item]。

FastAPI 将使用此 response_model 来：

    - 将输出数据转换为其声明的类型。
    - 校验数据。
    - 在 OpenAPI 的路径操作中为响应添加一个 JSON Schema。
    - 并在自动生成文档系统中使用。
    
但最重要的是：会将输出数据限制在该模型定义内。下面我们会看到这一点有多重要。

技术细节

    响应模型在参数中被声明，而不是作为函数返回类型的注解，这是因为路径函数可能不会真正返回该响应模型，
    而是返回一个 dict、数据库对象或其他模型，然后再使用 response_model 来执行字段约束和序列化。
'''


@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
    return item


@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
    return [
        {"name": "Portal Gun", "price": 42.0},
        {"name": "Plumbus", "price": 32.0},
    ]


# 返回与输入相同的数据
# 现在我们声明一个 UserIn 模型，它将包含一个明文密码属性。

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


# Don't do this in production!

@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
    '''
    永远不要存储用户的明文密码，也不要在响应中发送密码。
    现在，每当浏览器使用一个密码创建用户时，API 都会在响应中返回相同的密码。
    在这个案例中，这可能不算是问题，因为用户自己正在发送密码。
    但是，如果我们在其他的路径操作中使用相同的模型，则可能会将用户的密码发送给每个客户端。
    '''
    return user


# 添加输出模型¶
# 相反，我们可以创建一个有明文密码的输入模型和一个没有明文密码的输出模型
# 这里在输出的时候约束了一下，只返回部分字段
# 这样，即便我们的路径操作函数将会返回包含密码的相同输入用户，也会返回不包含密码的UserOut模型
class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


@app.post("/user_2/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
    return user


# 响应模型编码参数


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items_4/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    '''
        # 对于一些值，我们可以给定默认参数，有的时候没有存储实际的值，但是我们想从结果中忽略他们的默认值
        # 比如说当在Nosql数据库中保存了许多可选属性的模型，但是又不想发送充满默认值的很长的JSON响应
        # 这个时候就可以在装饰器中使用该参数使用response_model_exclude_unset=True参数，
        # 表示在返回响应数据时，排除那些未被显式设置的字段。也就是说，如果某个字段在请求数据中没有被提供（未被设置），那么这个字段将不会出现在响应数据中。

    '''
    return items[item_id]


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get("/yujiahao/{Hi}",
         response_model=Item,
         response_model_include={"name", "description"},
         # list也没关系
         response_model_exclude=["name"])
async def Hi_you(Hi: str):
    '''
    另外，还有两个比较有意思的，你可以指定包含哪些不包含哪些字段,它们接收一个由属性名称 str 组成的 set 来包含（忽略其他的）或者排除（包含其他的）这些属性。
        response_model_include 和 response_model_exclude,就说白了哪些要，哪些不要。

    【使用】
        {"name", "description"} 语法创建一个具有这两个值的 set。等同于 set(["name", "description"])。
        也就是说，万一是用 list或者tuple那么也不要紧，fastapi自己会帮我们转换为set,不会有什么影响的
    '''

    return items[Hi]
